Recovery系统的框架结构说明及常用的客制化修改流程

您所在的位置:网站首页 没有system recovery选项 Recovery系统的框架结构说明及常用的客制化修改流程

Recovery系统的框架结构说明及常用的客制化修改流程

2023-08-26 08:28| 来源: 网络整理| 查看: 265

一、Recovery系统简介

  Recovery模式指的是一种可以对安卓机内部的数据或系统进行修改的模式(类似于windows PE或DOS)。在这个模式下我们可以刷入新的Android系统,或者对已有的系统进行备份或升级,也可以在此模式下恢复出厂设置。系统进入recovery模式后会装载recovery分区,该分区包含recovery.img(与boot.img类似,也包含了标准的内核和根文件系统).进入该模式后主要就是运行了recovery服务(/sbin/recovery)。

二、Recovery系统的启动流程 2.1 进入Recovery系统的三种常见方法: 在关机情况下,同时按住电源(Power)+ 音量加(Vol +)键,直到出现Recovery界面为止。注:有的系统按键方式可能不同。使用安卓辅助工具,如:刷机精灵、360手机助手等等。使用adb命令reboot recovery启动。 2.2 从reboot启动到Recovery服务的流程

 在Bootloader开始如果没有组合键按下,就从MISC分区读取BCB块的command字段(在主系统时已经将“boot-recovery”写入)。然后就以Recovery模式开始启动。与正常启动不同的是Recovery模式下加载的镜像是recovery.img。这个镜像同boot.img类似,也包含了标准的内核和根文件系统。其后就与正常的系统启动类似,也是启动内核,然后启动文件系统。在进入文件系统后会执行/init,init的配置文件在bootable/recovery/etc/init.rc。这个文件的主要作用就是:

设置环境变量。建立etc连接。新建目录,备用。挂载/tmp为内存文件系统tmpfs启动recovery(/sbin/recovery)服务。启动adbd服务(用于调试)。

这里最重要的当然就是启动recovery服务了。 重启到recovery模式的流程图如下: recoveryRebootProcess.png

三、Recovery系统的框架结构 3.1 源码路径和主要原文件

 在Android源码环境中,recovery的源码主要在bootable/recovery文件夹下,另外在device目录下,会根据各个设备定制自己的接口以及UI界面。  在bootable/recovery目录下,主要的源文件有:

LOCAL_SRC_FILES := \ adb_install.cpp \ //设置usb驱动,升级系统 asn1_decoder.cpp \ //解码asn1格式 device.cpp \ //recovery的头部显示和列表项,和通过make_device方法实现一个device设备 fuse_sdcard_provider.cpp \ //加载升级文件升级 recovery.cpp \ //会最先执行recovery.cpp中的main方法,及清除data等方法 roots.cpp \ //进行进行分区挂载操作 rotate_logs.cpp \ //mstar添加的文件 screen_ui.cpp \ //界面的绘制文件,初始化UI等 ui.cpp \ //初始化输入设备,如初始化按键,背光等 verifier.cpp \ //签名验证的功能实现方法 wear_ui.cpp \ // 继承于ScreenRecoveryUI的UI wear_touch.cpp \ //界面的触摸事件响应

 该部分代码在编译后,会统一输出到 out/recovery/root/目录;

3.2 Recovery模式的三个部分

Recovery的工作需要整个软件平台的配合,从通信架构上来看,主要有三个部分。

MainSystem:即上面提到的正常启动模式(BCB中无命令情况),是引导boot.img启动的系统,Android的正常工作模式。更新时,在这种模式下的操作就是往 /cache/recovery/command 文件中写入ota升级命令及包存放路径。在重启进入Recovery模式之前,会向BCB中写入命令,以便在重启后告诉bootloader进入Recovery模式。Recovery:系统进入Recovery模式后会装载Recovery分区,该分区包含recovery.img。进入该模式后主要是运行Recovery服务(/sbin/recovery)来做相应的操作(重启、升级update.zip、擦除cache分区等)。Bootloader:除了正常的加载启动系统之外,还会通过读取MISC分区(BCB)获得来至Main system和Recovery的消息。 3.3 Recovery模式的两个通信接口

 在Recovery服务中上述的三个实体之间的通信是必不可少的,他们相互之间又有以下两个通信接口。

3.3.1 通过CACHE分区中的三个文件通信

Recovery通过/cache/recovery/目录下的三个文件与mian system通信。具体如下

/cache/recovery/command:这个文件保存着Main system传给Recovery的命令行,每一行就是一条命令,下表给出一些常用的命令及其含义:

命令取值含义send_intent字符串Recovery结束后将字符串写到这里,然后写入/cache/recovery/intent,比如升级结果update_package路径安装OTA升级包的路径wipe_data无擦除userdata以及cache,然后重启wipe_cache无擦除cache,然后重启set_encrypted_filesystemonoffjust_exit无退出和重启

/cache/recovery/last_log:Recovery模式在工作中的log打印。在recovery服务运行过程中,stdout以及stderr会重定位到/tmp/recovery.log在recovery退出之前会将其转存到/cache/recovery/last_log中,供查看。

/cache/recovery/intent:Recovery传递给Main system的信息。列如反馈升级是否成功。

3.3.2 通过BCB(Bootloader Control Block)通信

 BCB是bootloader与Recovery的通信接口,也是Bootloader与Main system之间的通信接口。存储在flash中的MISC分区,占用三个page,其本身就是一个结构体,具体成员以及各成员含义如下:

struct bootloader_message{ char command[32]; char status[32]; char recovery[1024]; }; command成员:其可能的取值我们在上文已经分析过了,即当我们想要在重启进入Recovery模式时,Main System会将boot-recovery命令写入。另外在退出Recovery时,会清除这个成员的值,防止重启时再次进入Recovery模式。status:在完成相应的更新后,Bootloader会将执行结果写入到这个字段。recovery:可被Main System写入,也可被Recovery服务程序写入。该文件的内容格式为: “recovery\n \n ”

 该文件存储的就是一个字符串,必须以recovery\n开头,否则这个字段的所有内容域会被忽略。“recovery\n”之后的部分,是/cache/recovery/command支持的命令。可以将其理解为Recovery操作过程中对命令操作的备份。Recovery对其操作的过程为:先读取BCB的recovery字段然后读取/cache/recovery /command,然后将二者重新写回BCB的recovery字段,这样在进入Main system之前,确保操作被执行。在操作之后进入Main system之前,Recovery又会清空BCB的command域和recovery域,这样确保重启后不再进入Recovery模式。

注意!这里比较容易弄混淆的点: BCB中的command和/cache/recovery/command的内容不等价,且不同类型。

BCB中的command:决定了bootloader该去引导启动android系统还是recovery系统BCB中的recovery:这个字段就是操作命令的备份,此处内容和/cache/recovery/command内容是等价的。

Recovery系统的三个部分和两个通信接口的示意图如下: RecoverySketchMap (1).png

四、Recovery的主要源码分析

 在进入文件系统后会执行bootable/recovery/etc/init.rc,在init.rc中下面代码可知,进入recovery模式后会执行sbin/recovery,此文件是bootable/recovery.cpp生成的(查看Android.mk可知),所以recovery.cpp是recovery模式的入口。

service recovery /sbin/recovery seclabel u:r:recovery:s0

 因为recovery.cpp的main函数太长了,这里分块分析recovery的主要源码,其实在main函数中主要做了下面几件事情:

设置adb进程。log重定向到recovery.log。装载分区表,填充fstab结构体。读取控制参数。加载语言显示。加载UI模型。死循环prompt_and_wait,等待操作;退出recovery模式

 recovery.cpp的main方法执行的流程图大概如下: RecoveryMainTimingDiagram.png

4.1 设置adb进程

 在recovery的main方法中首先判断命令行参数是否为–adbd,如果有则执行minadbd_main函数,这样是为了方便使用adb sideload命令,如果参数为-adbd的话,那么它会变成精简版adbd,只支持sideload命令。

if (argc == 2 && strcmp(argv[1], "--adbd") == 0) { minadbd_main(); return 0; } 4.2 输出log重定向到recovery.log

 重定向标准输出和标准出错log到/tmp/recovery.log这个文件里,这个文件是临时log文件,在recovery模式finish的时候会将这个文件里面的log保存到/cache/recovery/last_log中。为了方便调试,可以将临时log重定位到控制台输出,修改参数:static const char ``*TEMPORARY_LOG_FILE = ``"/dev/console"``;

redirect_stdio(TEMPORARY_LOG_FILE); 4.3 装载分区表

 之后会调用roots.cpp文件中的load_volume_table()方法来初始化并装载recovery的分区表到fstab结构体中,load_volume_table()方法如下:

roots.cpp void load_volume_table() { int i; int ret; //加载分区表到fstab,具体就是去加载/etc/recovery.fstab这个文件,是Vold进程中的函数 fstab = fs_mgr_read_fstab_default(); if (!fstab) { LOG(ERROR) Volume* v = &fstab->recs[i]; printf(" %d %s %s %s %lld\n", i, v->mount_point, v->fs_type, v->blk_device, v->length); } printf("\n"); }

 上面提到的Vold进程是在kernel初始化的时候启动的,所有的热插拔设备都是通过Vold 进程挂载的,Vold的入口是/system/vold/main.cpp文件的main函数,fs_mgr_read_fstab_default()方法就是去解析/etc/recovery.fstab这个文件,上面具体log如下:

0 /vendor ext4 /dev/block/platform/mstar_mci.0/by-name/vendor 0 [ 12.959471] 1 /system ext4 /dev/block/platform/mstar_mci.0/by-name/system 0 [ 12.959476] 2 /system ext4 /dev/block/platform/mstar_mci.0/by-name/system 0 [ 12.959491] 3 /data ext4 /dev/block/platform/mstar_mci.0/by-name/userdata 0 [ 12.959496] 4 /cache ext4 /dev/block/platform/mstar_mci.0/by-name/cache 0 [ 12.959501] 5 /vendor ext4 /dev/block/platform/mstar_mci.0/by-name/vendor 0 [ 12.959506] 6 /tvservice ext4 /dev/block/platform/mstar_mci.0/by-name/tvservice 0 [ 12.959511] 7 /tvconfig ext4 /dev/block/platform/mstar_mci.0/by-name/tvconfig 0 [ 12.959517] 8 /tvdatabase ext4 /dev/block/platform/mstar_mci.0/by-name/tvdatabase 0 [ 12.959522] 9 /tvcustomer ext4 /dev/block/platform/mstar_mci.0/by-name/tvcustomer 0 [ 12.959527] 10 /tvcertificate ext4 /dev/block/platform/mstar_mci.0/by-name/tvcertificate 0 [ 12.959532] 11 /boot1 emmc /dev/block/mmcblk0boot0 0 [ 12.959537] 12 /boot2 emmc /dev/block/mmcblk0boot1 0 [ 12.959542] 13 /MBOOT emmc /dev/block/platform/mstar_mci.0/by-name/MBOOT 0 [ 12.959547] 14 /MPOOL emmc /dev/block/platform/mstar_mci.0/by-name/MPOOL 0 [ 12.959552] 15 /misc emmc /dev/block/platform/mstar_mci.0/by-name/misc 0 [ 12.959556] 16 /recovery emmc /dev/block/platform/mstar_mci.0/by-name/recovery 0 [ 12.959561] 17 /boot emmc /dev/block/platform/mstar_mci.0/by-name/boot 0 [ 12.959566] 18 /tee emmc /dev/block/platform/mstar_mci.0/by-name/tee 0 [ 12.959571] 19 /RTPM emmc /dev/block/platform/mstar_mci.0/by-name/RTPM 0 [ 12.959576] 20 /dtb emmc /dev/block/platform/mstar_mci.0/by-name/dtb 0 [ 12.959581] 21 /optee emmc /dev/block/platform/mstar_mci.0/by-name/optee 0 [ 12.959586] 22 /armfw emmc /dev/block/platform/mstar_mci.0/by-name/armfw 0 [ 12.959591] 23 auto auto /devices/platform/mstar_fcie* 0 [ 12.959595] 24 auto auto /devices/platform/mstar_sdio* 0 [ 12.959600] 25 auto auto /devices/Mstar-ehci* 0 [ 12.959605] 26 auto auto /devices/Mstar-xhci* 0 [ 12.959610] 27 /tmp ramdisk ramdisk 0

 挂载完相应的分区以后,就需要获取命令参数,因为只有挂载了对应的分区,才能访问到记录操作命令的/cache/recovery/command这个文件及BCB块,如果分区都没找到,那么当然就找不到分区上的文件,挂载分区这个步骤是至关重要的。

//从上面建立的分区表信息中读取是否有cache分区,因为log等重要信息都存在cache分区里 has_cache = volume_for_path(CACHE_ROOT) != nullptr; // MStar Android Patch Begin if(has_cache){ //mstar添加的确定是否有cache分区的方法 ensure_path_mounted(CACHE_ROOT); } // MStar Android Patch End 4.4 读取控制参数

 在main方法中通过get_args方法获取启动参数。

//从传入的参数或/cache/recovery/command文件中得到相应的命令 std::vector args = get_args(argc, argv);

 recovery和bootloader要通过/misc才能相互通信,对应的信息数据结构体为bootloader_message;get_args(argc,argv)方法如下:

struct bootloader_message{ char command[32];//bootloader 启动时读取改数据,决定是否进入recovery模式 char status[32];//由bootloader进行更新,标识升级的结果; char recovery[768];//recovery要执行的命令,recovery从中读取信息; char stage[32]; // 恢复字段,它仅用于存储恢复命令行 char reserved[1148]; // 保留字段 }; static std::vector get_args(const int argc, char** const argv) { CHECK_GT(argc, 0); bootloader_message boot = {};//参数结构体 std::string err; if (!read_bootloader_message(&boot, &err)) { // 从BCB中获取参数,这里有可能是为空的情况。 LOG(ERROR) boot.recovery[sizeof(boot.recovery) - 1] = '\0'; // Ensure termination std::string boot_recovery(boot.recovery); std::vector tokens = android::base::Split(boot_recovery, "\n"); if (!tokens.empty() && tokens[0] == "recovery") { for (auto it = tokens.begin() + 1; it != tokens.end(); it++) { // Skip empty and '\0'-filled tokens. if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); } LOG(INFO) std::string content; if (ensure_path_mounted(COMMAND_FILE) == 0 && android::base::ReadFileToString(COMMAND_FILE, &content)) { std::vector tokens = android::base::Split(content, "\n"); // All the arguments in COMMAND_FILE are needed (unlike the BCB message, // COMMAND_FILE doesn't use filename as the first argument). for (auto it = tokens.begin(); it != tokens.end(); it++) { // Skip empty and '\0'-filled tokens. if (!it->empty() && (*it)[0] != '\0') args.push_back(std::move(*it)); } LOG(INFO) switch (arg) { case 'n': android::base::ParseInt(optarg, &retry_count, 0); break; case 'u': update_package = optarg; break; case 'w': should_wipe_data = true; break; case 'c': should_wipe_cache = true; break; case 't': show_text = true; break; case 's': sideload = true; break; case 'a': sideload = true; sideload_auto_reboot = true; break; case 'x': just_exit = true; break; case 'l': locale = optarg; break; case 'p': shutdown_after = true; break; case 'r': reason = optarg; break; case 'e': security_update = true; break; case 0: { std::string option = OPTIONS[option_index].name; if (option == "wipe_ab") { should_wipe_ab = true; } else if (option == "wipe_package_size") { android::base::ParseUint(optarg, &wipe_package_size); } else if (option == "prompt_and_wipe_data") { should_prompt_and_wipe_data = true; } break; } // MStar Android Patch Begin case 'd': dev_uuid = optarg; break; case 'b': dev_label= optarg; break; // MStar Android Patch Begin case '?': LOG(ERROR) locale = load_locale_from_cache(); } if (locale.empty()) { locale = DEFAULT_LOCALE; } } 4.6 加载UI模式

加载UI界面的流程大概有下面几步:

新建一个Device类的对象;调用Device类的GetUI()返回一个RecoveryUI对象,这里应该返回的是ScreenRecoveryUI,ScreenRecoveryUI继承于RecoveryUI;调用Init()初始化UI;调用RecoveryUi的init方法去设置国家语言,然后初始化输入设备,并创建一个线程用于监听输入事件;调用minui库的gr_init方法初始化图形显示,主要是打开设备、分配内存、初始化一些参数;通过LoadBitmap()加载png图片生成surface对象创建一个子线程更新progress进度条;调用SetBackground方法设置背景图片;

 Recovery中显示UI界面的framebuffer使用的是minui库,该库在网上也能查到相应的方法说明,下面会有详细介绍。

4.6.1 在main方法中

这里主要做了这几件事情:

新建一个device设备;获取到UI;调用UI的Init方法进行初始化;设置背景; Device* device = make_device();//新建一个Device设备 if (android::base::GetBoolProperty("ro.boot.quiescent", false)) { //如果是静态UI模式则进入这里 printf("Quiescent recovery mode.\n"); ui = new StubRecoveryUI(); } else { ui = device->GetUI();//获取到ScreenRecoveryUI if (!ui->Init(locale)) {//调用ScreenRecoveryUI::init方法 printf("Failed to initialize UI, use stub UI instead.\n"); ui = new StubRecoveryUI(); } } // Set background string to "installing security update" for security update, // otherwise set it to "installing system update". // 设置背景字符串为“正在安装安全跟新”或者“正在安装系统更新”,这个在后面会根据状态更新的 ui->SetSystemUpdateText(security_update); int st_cur, st_max; if (!stage.empty() && sscanf(stage.c_str(), "%d/%d", &st_cur, &st_max) == 2) { ui->SetStage(st_cur, st_max); } ui->SetBackground(RecoveryUI::NONE);//设置背景,这里没有背景 if (show_text) ui->ShowText(true); // 判断界面上是否能显示字符 //设置selinux权限 sehandle = selinux_android_file_context_handle(); selinux_android_set_sehandle(sehandle); if (!sehandle) { ui->Print("Warning: No file_contexts\n"); } //虚函数,什么都没有做 device->StartRecovery(); printf("Command:"); for (const auto& arg : args) { printf(" \"%s\"", arg.c_str()); } printf("\n\n"); 4.6.2 调用UI的Init方法初始化

 这里获取到的UI是ScreenRecoveryUI,所以调用的是ScreenRecoveryUI::Init,代码在screen_ui.cpp中,ScreenRecoveryUI是继承于RecoveryUI的,这个方法里面会去初始化RecoveryUI和minui的图形显示,之后就是加载图片资源为surface对象,并创建一个子线程用来更新升级的进度条。

bool ScreenRecoveryUI::Init(const std::string& locale) { RecoveryUI::Init(locale);//调用RecoveryUI的Init方法,这个方法里面会设置语言,及初始化输入设备和事件,并创建一个子线程去监听输入事件 if (!InitTextParams()) {//这里初始化文本参数,以及调用minui中的Init方法初始化图形显示,主要是打开设备、分配内存、初始化一些参数 ; return false; } //设置屏幕密度 density_ = static_cast(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f; // Are we portrait or landscape? layout_ = (gr_fb_width() > gr_fb_height()) ? LANDSCAPE : PORTRAIT; // Are we the large variant of our base layout? if (gr_fb_height() > PixelsFromDp(800)) ++layout_; text_ = Alloc2d(text_rows_, text_cols_ + 1); file_viewer_text_ = Alloc2d(text_rows_, text_cols_ + 1); menu_ = Alloc2d(text_rows_, text_cols_ + 1); text_col_ = text_row_ = 0; text_top_ = 1; //LoadBitmap()方法将png生成surface, 每个png图片对应一个surface LoadBitmap("icon_error", &error_icon); LoadBitmap("progress_empty", &progressBarEmpty); LoadBitmap("progress_fill", &progressBarFill); LoadBitmap("stage_empty", &stageMarkerEmpty); LoadBitmap("stage_fill", &stageMarkerFill); // Background text for "installing_update" could be "installing update" // or "installing security update". It will be set after UI init according // to commands in BCB. installing_text = nullptr; LoadLocalizedBitmap("erasing_text", &erasing_text); //LoadLocalizedBitmap()将相应文字所在的图片中的text信息根据当前的locale提取出来,生成对应的surface LoadLocalizedBitmap("no_command_text", &no_command_text); LoadLocalizedBitmap("error_text", &error_text); LoadAnimation();//这里是去加载升级动画的那个圆动画 //创建一个线程,在该循环中不停地检测progressBarType来决定是不是要更新进度条 pthread_create(&progress_thread_, nullptr, ProgressThreadStartRoutine, this); return true; } 4.6.3 RecoveryUI::Init方法

 这个方法里面会设置语言,及初始化输入设备和事件,并创建一个子线程去监听输入事件,这里就不详细介绍了;

bool RecoveryUI::Init(const std::string& locale) { // Set up the locale info. SetLocale(locale);//设置文本语言 //初始化输入设备 ev_init(std::bind(&RecoveryUI::OnInputEvent, this, std::placeholders::_1, std::placeholders::_2)); ev_iterate_available_keys(std::bind(&RecoveryUI::OnKeyDetected, this, std::placeholders::_1)); if (!InitScreensaver()) { LOG(INFO) //这里就是调用minui\graphics.cpp中的Init方法 return false; } gr_font_size(gr_sys_font(), &char_width_, &char_height_);//初始化字体大小等 text_rows_ = gr_fb_height() / char_height_; text_cols_ = gr_fb_width() / char_width_; return true; } ----------------------------------------------------------------------------------------- minui\graphics_fbdev.cpp文件: GRSurface* MinuiBackendFbdev::Init() { int fd = open("/dev/graphics/fb0", O_RDWR);//打开/dev/graphics/fb0设备,并要读写权限 if (fd == -1) {//打开失败 perror("cannot open fb0"); return nullptr; } fb_fix_screeninfo fi; if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) //获取VideoScreen信息到vi perror("failed to get fb0 info"); close(fd); return nullptr; } // We print this out for informational purposes only, but // throughout we assume that the framebuffer device uses an RGBX // pixel format. This is the case for every development device I // have access to. For some of those devices (eg, hammerhead aka // Nexus 5), FBIOGET_VSCREENINFO *reports* that it wants a // different format (XBGR) but actually produces the correct // results on the display when you write RGBX. // // If you have a device that actually *needs* another pixel format // (ie, BGRX, or 565), patches welcome... printf( "fb0 reports (possibly inaccurate):\n" " vi.bits_per_pixel = %d\n" " vi.red.offset = %3d .length = %3d\n" " vi.green.offset = %3d .length = %3d\n" " vi.blue.offset = %3d .length = %3d\n", vi.bits_per_pixel, vi.red.offset, vi.red.length, vi.green.offset, vi.green.length, vi.blue.offset, vi.blue.length); //映射FrameBuffer到bits void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (bits == MAP_FAILED) { perror("failed to mmap framebuffer"); close(fd); return nullptr; } //初始化bits memset(bits, 0, fi.smem_len); //设置FrameBuffer的参数 gr_framebuffer[0].width = vi.xres; gr_framebuffer[0].height = vi.yres; gr_framebuffer[0].row_bytes = fi.line_length; gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8; gr_framebuffer[0].data = static_cast(bits); //初始化gr_framebuffer[0].data memset(gr_framebuffer[0].data, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes); /* check if we can use double buffering */ //检查是否可以用双缓冲 if (vi.yres * fi.line_length * 2 double_buffered = false; // Without double-buffering, we allocate RAM for a buffer to // draw in, and then "flipping" the buffer consists of a // memcpy from the buffer we allocated to the framebuffer. //初始化GRSurface,将帧缓存中的数据复制到gr_draw中 gr_draw = static_cast(malloc(sizeof(GRSurface))); memcpy(gr_draw, gr_framebuffer, sizeof(GRSurface)); gr_draw->data = static_cast(malloc(gr_draw->height * gr_draw->row_bytes)); if (!gr_draw->data) { perror("failed to allocate in-memory surface"); return nullptr; } } //初始化gr_draw的data数据 memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes); fb_fd = fd; //设置显示帧缓存区 SetDisplayedFramebuffer(0); printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height); Blank(true); Blank(false); return gr_draw; } 4.6.5 minui库的方法说明 int gr_init(void); /* 初始化图形显示,主要是打开设备、分配内存、初始化一些参数 */ void gr_exit(void); /* 注销图形显示,关闭设备并释放内存 */ int gr_fb_width(void); /* 获取屏幕的宽度 */ int gr_fb_height(void); /* 获取屏幕的高度 */ gr_pixel *gr_fb_data(void); /* 获取显示数据缓存的地址 */ void gr_flip(void); /* 刷新显示内容 */ void gr_fb_blank(bool blank); /* 清屏 */ void gr_color(unsigned char r, unsigned char g, unsigned char b, unsigned char a); /* 设置字体颜色 */ void gr_fill(int x, int y, int w, int h); /* 填充矩形区域,参数分别代表起始坐标、矩形区域大小 */ int gr_text(int x, int y, const char *s); /* 显示字符串 */ int gr_measure(const char *s); /* 获取字符串在默认字库中占用的像素长度 */ void gr_font_size(int *x, int *y); /* 获取当前字库一个字符所占的长宽 */ void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy); /* 填充由source指定的图片 */ unsigned int gr_get_width(gr_surface surface); /* 获取图片宽度 */ unsigned int gr_get_height(gr_surface surface); /* 获取图片高度 */ /* 根据图片创建显示资源数据,name为图片在mk文件指定的相对路径 */ int res_create_surface(const char* name, gr_surface* pSurface); void res_free_surface(gr_surface surface); /* 释放资源数据 */ 4.7 死循环prompt_and_wait

 再加载完UI模式之后,如果在开始读取的控制参数为空的话就会执行到prompt_and_wait方法,prompt_and_wait()函数是个死循环,开始显示recovery选项 并处理用户通过按键或者触摸屏的选项,如Reboot system等。

Device::BuiltinAction after = shutdown_after ? Device::SHUTDOWN : Device::REBOOT; //status为none,代表没有命令,会执行此if语句 if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) || ui->IsTextVisible()) { //prompt_and_wait()函数是个死循环 开始显示recovery选项 并处理用户通过按键或者触摸屏的选项,如Reboot system等 //这里返回的temp就是操作的Action Device::BuiltinAction temp = prompt_and_wait(device, status); if (temp != Device::NO_ACTION) { after = temp; } }

 prompt_and_wait(device,status)函数具体如下:

// Returns REBOOT, SHUTDOWN, or REBOOT_BOOTLOADER. Returning NO_ACTION means to take the default, // which is to reboot or shutdown depending on if the --shutdown_after flag was passed to recovery. static Device::BuiltinAction prompt_and_wait(Device* device, int status) { //一个死循环 for (;;) { //finish_recovery()方法做的事情是,清除清除BCB中的命令,保存当前的locale语言信息到/cache,并保存log到/cache //这里相当于启动选择菜单的初始化 finish_recovery(); // MStar Android Patch Begin //确保有cache分区 ensure_path_mounted(CACHE_ROOT); // MStar Android Patch End switch (status) { case INSTALL_SUCCESS: case INSTALL_NONE: ui->SetBackground(RecoveryUI::NO_COMMAND); break; case INSTALL_ERROR: case INSTALL_CORRUPT: ui->SetBackground(RecoveryUI::ERROR); break; } //设置进度条为空 ui->SetProgressType(RecoveryUI::EMPTY); //获取选择的item选项的Action int chosen_item = get_menu_selection(nullptr, device->GetMenuItems(), false, 0, device); // Device-specific code may take some action here. It may return one of the core actions // handled in the switch statement below. Device::BuiltinAction chosen_action = (chosen_item == -1) ? Device::REBOOT : device->InvokeMenuItem(chosen_item); bool should_wipe_cache = false; //根据Action来执行操作 switch (chosen_action) { case Device::NO_ACTION: break; case Device::REBOOT: case Device::SHUTDOWN: case Device::REBOOT_BOOTLOADER: //返回item的Action return chosen_action; case Device::WIPE_DATA: if (ui->IsTextVisible()) { if (ask_to_wipe_data(device)) { wipe_data(device); } } else { wipe_data(device); return Device::NO_ACTION; } break; // MStar Android Patch Begin case Device::APPLY_CACHE: { // why do unmount system?one case:from setting select local upgrade,enter recovery mode do OTA upgrade,upgrading system // plug U disk,system partition is mountting;then use IR select "apply update from cache' in recovery mode. // do OTA upgrade again,if dont unmount system,then when execute OTA upgrade-script to fromat system partition,it will fail. // Associated with the mantis 0515614 ensure_path_unmounted("/system"); status = apply_from_cache(device, &should_wipe_cache); if (status == INSTALL_SUCCESS && should_wipe_cache) { if (!wipe_cache(false, device)) { status = INSTALL_ERROR; } } if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); copy_logs(); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } else { ui->Print("\nInstall from cache complete.\n"); } } break; // MStar Android Patch End case Device::WIPE_CACHE: wipe_cache(ui->IsTextVisible(), device); if (!ui->IsTextVisible()) return Device::NO_ACTION; break; case Device::APPLY_ADB_SIDELOAD: case Device::APPLY_SDCARD: { bool adb = (chosen_action == Device::APPLY_ADB_SIDELOAD); if (adb) { status = apply_from_adb(ui, &should_wipe_cache, TEMPORARY_INSTALL_FILE); } else { //status = apply_from_sdcard(device, &should_wipe_cache); // MStar Android Patch Begin status = apply_from_external_stroage(device, &should_wipe_cache); // MStar Android Patch End } if (status == INSTALL_SUCCESS && should_wipe_cache) { if (!wipe_cache(false, device)) { status = INSTALL_ERROR; } } if (status != INSTALL_SUCCESS) { ui->SetBackground(RecoveryUI::ERROR); ui->Print("Installation aborted.\n"); copy_logs(); } else if (!ui->IsTextVisible()) { return Device::NO_ACTION; // reboot if logs aren't visible } else { ui->Print("\nInstall from %s complete.\n", adb ? "ADB" : "SD card"); } } break; case Device::VIEW_RECOVERY_LOGS: choose_recovery_file(device); break; case Device::RUN_GRAPHICS_TEST: run_graphics_test(); break; case Device::MOUNT_SYSTEM: // For a system image built with the root directory (i.e. system_root_image == "true"), we // mount it to /system_root, and symlink /system to /system_root/system to make adb shell // work (the symlink is created through the build system). (Bug: 22855115) if (android::base::GetBoolProperty("ro.build.system_root_image", false)) { if (ensure_path_mounted_at("/", "/system_root") != -1) { ui->Print("Mounted /system.\n"); } } else { if (ensure_path_mounted("/system") != -1) { ui->Print("Mounted /system.\n"); } } break; } } } 4.8 退出Recovery

 在通过prompt_and_wait函数处理用户通过按键或者触摸屏的选项后,会返回当前选项的Action,然后赋值给after,在main函数中会通过after的值来确定怎么结束recovery模式;

if ((status != INSTALL_SUCCESS && status != INSTALL_SKIPPED && !sideload_auto_reboot) || ui->IsTextVisible()) { Device::BuiltinAction temp = prompt_and_wait(device, status);//这里返回的item if (temp != Device::NO_ACTION) { after = temp; } } // Save logs and clean up before rebooting or shutting down. // 保存日志到/cache,清除BCB中的命令,保存locale语言信息 finish_recovery(); switch (after) { //如果是关机则会设置ANDROID_RB_PROPERTY为shutdown case Device::SHUTDOWN: ui->Print("Shutting down...\n"); android::base::SetProperty(ANDROID_RB_PROPERTY, "shutdown,"); break; //重启到bootloader case Device::REBOOT_BOOTLOADER: ui->Print("Rebooting to bootloader...\n"); android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,bootloader"); break; //其他的默认重启 default: ui->Print("Rebooting...\n"); reboot("reboot,"); break; } 五、Recovery最主要的功能-升级

 在应用层层面的ota升级包的下载、校验以及最后通过framework层接口发起安装过程这里就不详细介绍了。在这里,主要介绍进入Recovery模式后,OTA包的升级过程。  首先,在应用层下载升级包后,会调用RecoverySystem.installPackage(Context context, File packageFile)函数来发起安装过程,这个过程主要的原理就是往 /cache/recovery/command 写入ota升级命令及包存放路径,然后重启到recovery模式,升级命令大概为update_package=/mnt/sdcard/update.zip,重启到recovery就是在BCB中的command字段写入boot-recovery。 进入Recovery模式后OTA包升级的时序图如下: RecoveryOTA (1).png

5.1 recovery.cpp的main方法

 进入recovery模式后在recovery.cpp的main函数中会通过get_args(argc,argv)方法读取控制参数,这里再4.4章节有讲到,这个方法会去读取/cacha/recovery/command文件构建启动参数,然后在while循序中会解析控制参数,OTA升级的话解析参数后会设置update_package不为空,然后再main函数中就会进入如下流程:

//update_package参数不为空的话进入 if (update_package != NULL) { // It's not entirely true that we will modify the flash. But we want // to log the update attempt since update_package is non-NULL. modified_flash = true; //这里是判断电量是否允许进行升级 if (!is_battery_ok()) { ui->Print("battery capacity is not enough for installing package, needed is %d%%\n", BATTERY_OK_PERCENTAGE); // Log the error code to last_install when installation skips due to // low battery. log_failure_code(kLowBattery, update_package); status = INSTALL_SKIPPED; } else if (bootreason_in_blacklist()) {//这里是判断是否是从需要跳过升级的意图启动的 // Skip update-on-reboot when bootreason is kernel_panic or similar ui->Print("bootreason is in the blacklist; skip OTA installation\n"); log_failure_code(kBootreasonInBlacklist, update_package); status = INSTALL_SKIPPED; } else { //ota升级流程会进入install_package方法 status = install_package(update_package, &should_wipe_cache, TEMPORARY_INSTALL_FILE, true, retry_count); //判断是否升级成功及是否清除了cache if (status == INSTALL_SUCCESS && should_wipe_cache) { wipe_cache(false, device); } //如果升级失败则进入 if (status != INSTALL_SUCCESS) { ui->Print("Installation aborted.\n"); // When I/O error happens, reboot and retry installation EIO_RETRY_COUNT // times before we abandon this OTA update. if (status == INSTALL_RETRY && retry_count ui->Print("Reboot failed\n"); } else { while (true) { pause(); } } } // If this is an eng or userdebug build, then automatically // turn the text display on if the script fails so the error // message is visible. if (is_ro_debuggable()) { ui->ShowText(true); } } } }

 从上面的流程看,会进入install_package方法。

5.2 install_package方法

 install_package方法就是设置安装框架然后调用了really_install_package方法。之后还有一些log输出,最后返回升级的结果。

int install_package(const char* path, bool* wipe_cache, const char* install_file, bool needs_mount, int retry_count) { modified_flash = true; auto start = std::chrono::system_clock::now();//记录开始时间 int start_temperature = GetMaxValueFromThermalZone(); int max_temperature = start_temperature; int result; std::vector log_buffer; //这里设置安装框架,如果设置失败则放回error if (setup_install_mounts() != 0) { LOG(ERROR) ui->SetBackground(RecoveryUI::INSTALLING_UPDATE); ui->Print("Finding update package...\n"); // Give verification half the progress bar... ui->SetProgressType(RecoveryUI::DETERMINATE); ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME); LOG(INFO) LOG(ERROR) return INSTALL_NONE; } } ..... }

2、确保升级包所在的分区已经挂载

static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount, std::vector& log_buffer, int retry_count, int* max_temperature) { ..... if (path && needs_mount) { if (path[0] == '@') { ensure_path_mounted(path+1); } else { ensure_path_mounted(path); } } ..... }

3、获取升级包地址

static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount, std::vector& log_buffer, int retry_count, int* max_temperature) { ..... MemMapping map; if (!map.MapFile(path)) { LOG(ERROR) log_buffer.push_back(android::base::StringPrintf("error: %d", kZipVerificationFailure)); sysReleaseMap(&map); return INSTALL_CORRUPT; } ..... }

5、打开升级包

static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount, std::vector& log_buffer, int retry_count, int* max_temperature) { ..... // Try to open the package. ZipArchiveHandle zip; int err = OpenArchiveFromMemory(map.addr, map.length, path, &zip); if (err != 0) { LOG(ERROR) log_buffer.push_back(android::base::StringPrintf("error: %d", kPackageCompatibilityFailure)); sysReleaseMap(&map); CloseArchive(zip); return INSTALL_CORRUPT; } ..... }

7、执行升级脚本文件,开始升级,并返回升级结果

static int really_install_package(const char *path, bool* wipe_cache, bool needs_mount, std::vector& log_buffer, int retry_count, int* max_temperature) { ..... ui->SetEnableReboot(false); int result = try_update_binary(path, zip, wipe_cache, log_buffer, retry_count, max_temperature); ui->SetEnableReboot(true); ui->Print("\n"); sysReleaseMap(&map); CloseArchive(zip); return result; }

 这里看到最终会调到try_update_binary方法,try_update_binary是真正实现对升级包进行升级的函数。

5.4 try_update_binary()真正实现升级的函数

总的来说,try_update_binary主要做了以下几个操作:

update_binary_command:解析升级包,将一些文件描述信息保存到args中。fork创建一个子进程 , 使用系统调用函数execv( ) 去执行/tmp/update-binary程序,update-binary: 这个是Recovery OTA升级的核心程序,是一个二进制文件,实现代码位于系统源码bootable/updater.cpp。其实质是相当于一个脚本解释器,能够识别updater-script中描述的操作并执行。updater-script:updater-script是我们升级时所具体使用到的脚本文件,具体描述了更新过程,它主要用以控制升级流程的主要逻辑。具体位置位于升级包中/META-INF/com/google/android/update-script,在我们制作升级包的时候产生。在升级的时候,由update_binary程序从升级包里面解压到内存文件系统的/tmp/update_script中,并按照update_script里面的命令,对系统进行升级。 static int try_update_binary(const char* path, ZipArchiveHandle zip, bool* wipe_cache, std::vector& log_buffer, int retry_count, int* max_temperature) { read_source_target_build(zip, log_buffer);//这里是去读取上一步打开的updata文件 int pipefd[2]; pipe(pipefd); std::vector args; //去解析updata文件,其中包括解析update_binary存放路径、Recovery版本号、升级包存放路径等数据,然后存放在args中 int ret = update_binary_command(path, zip, retry_count, pipefd[1], &args); if (ret) { close(pipefd[0]); close(pipefd[1]); return ret; } //将args中的数据赋值给chr_args,方便后面子进程使用execv去调用update-binary执行升级操作 const char* chr_args[args.size() + 1]; chr_args[args.size()] = nullptr; for (size_t i = 0; i //fork失败 close(pipefd[0]); close(pipefd[1]); PLOG(ERROR) //这里通过line.find_first_of(" \n");来获取一条命令 std::string line(buffer); size_t space = line.find_first_of(" \n"); std::string command(line.substr(0, space)); if (command.empty()) continue; // Get rid of the leading and trailing space and/or newline. std::string args = space == std::string::npos ? "" : android::base::Trim(line.substr(space)); if (command == "show_progress") {//设置进度条进度,有动画效果的,这些命令的意思后面有一个表格说明 // MStar Android Patch Begin // check usb device is unpluged or not. // if usb device is unpluged during installing ota upgrade package, we should send fail message to user. if ((0 == strncmp(path, "/mnt/", strlen("/mnt/"))) || (0 == strncmp(path, "/storage/", strlen("/storage/")))){ if (-1 == check_usb_device(path)){ LOG(ERROR) LOG(ERROR) ui->SetProgress(fraction); } else { LOG(ERROR) //设置清除缓存为true *wipe_cache = true; } else if (command == "clear_display") { ui->SetBackground(RecoveryUI::NONE); } else if (command == "enable_reboot") { // packages can explicitly request that they want the user // to be able to reboot during installation (useful for // debugging packages that don't exit). ui->SetEnableReboot(true); } else if (command == "retry_update") { retry_update = true; } else if (command == "log") { if (!args.empty()) { // Save the logging request from updater and write to last_install later. log_buffer.push_back(args); } else { LOG(ERROR) //安装重试 return INSTALL_RETRY; } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { LOG(ERROR) int fd = open("/dev/graphics/fb0", O_RDWR); if (fd == -1) { perror("cannot open fb0"); return nullptr; } ..... memset(gr_draw->data, 0, gr_draw->height * gr_draw->row_bytes); fb_fd = fd; SetDisplayedFramebuffer(0); printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height); Blank(true); Blank(false); //主要就是修改的这里 //return gr_draw; //hekh add for rotateCanvas return rotate_canvas_get(gr_draw); } -------------------------------------------------------------------------------------- GRSurface *rotate_canvas_get(GRSurface *gr_draw) { // Initialize the canvas, if it was not exist. if (gr_canvas==NULL) rotate_canvas_init(gr_draw); return gr_canvas; } #define swap(x, y, type) {type z; z=x; x=y; y=z;} void rotate_canvas_init(GRSurface *gr_draw) { gr_canvas = &__gr_canvas; memcpy(gr_canvas, gr_draw, sizeof(GRSurface)); // 旋转90度和270度就交互宽高 if (rotate_config(gr_draw)%2) { swap(gr_canvas->width, gr_canvas->height, int); gr_canvas->row_bytes = gr_canvas->width * gr_canvas->pixel_bytes; } gr_canvas->data = (unsigned char*) malloc(gr_canvas->height * gr_canvas->row_bytes); if (gr_canvas->data == NULL) { printf("[graphics] rotate_canvas_init() malloc gr_canvas->data failed\n"); gr_canvas = NULL; return; } memset(gr_canvas->data, 0, gr_canvas->height * gr_canvas->row_bytes); print_surface_info(gr_draw, "gr_draw"); print_surface_info(gr_canvas, "gr_canvas"); } -------------------------------------------------------------------------------------- static int rotate_config(GRSurface *gr_draw) { if (rotate_index //hekh add for rotateCanvas //主要添加的这个方法,传入的第一个参数是要传入内核的GRSurface,所以他的宽高和屏幕一样, //第二个参数就是旋转了宽高的GRSurface,但内部的数据方向还没有改变 rotate_surface(gr_draw, rotate_canvas_get(gr_draw)); if (double_buffered) { // Change gr_draw to point to the buffer currently displayed, // then flip the driver so we're displaying the other buffer // instead. gr_draw = gr_framebuffer + displayed_buffer; SetDisplayedFramebuffer(1 - displayed_buffer); } else { // Copy from the in-memory surface to the framebuffer. memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes); } //return gr_draw; //hekh add for rotateCanvas //这里还是返回旋转了宽高的GRSurface return rotate_canvas_get(gr_draw); } --------------------------------------------------------------------------------------- void rotate_surface(GRSurface *dst, GRSurface *src) { rotate_surface_t rotate; rotate=rotate_func[rotate_config(dst)]; rotate(dst, src); } typedef void (*rotate_surface_t) (GRSurface *, GRSurface *); rotate_surface_t rotate_func[4]= { rotate_surface_0, rotate_surface_90, rotate_surface_180, rotate_surface_270 }; //这里就看看旋转90度的数据转换是怎么转换的 static void rotate_surface_90(GRSurface *dst, GRSurface *src) { int w, k, h; unsigned int *src_pixel; unsigned int *dst_pixel; for (h=0; hheight; h++) { for (w=0, k=src->height-1; wwidth; w++, k--) { dst_pixel = (unsigned int *)(dst->data + dst->row_bytes*h); src_pixel = (unsigned int *)(src->data + src->row_bytes*k); *(dst_pixel+w)=*(src_pixel+h); } } }

转换framebuffer的数据具体的操作流程如下图: Recoveryrotate_surface_90(1).png  说明:第一个循环h=0,w=0,k=src->height-1,dst->data + dst->row_bytes*h=data,这个data是一个指针指向的是dst的图形数据的一个字节,所以dst_pixel就指向了蓝色的那个字节,row_bytes是每一行的字节数,同理src_pixel就指向了src中蓝色的字节,然后因为w和h都是0,所以就把两个蓝色字节的数据交换了,第二次循环,h=0;w=1;k=src->height-2;所以同理dst_pixel还是指向的蓝色字节,src_pixel指向的src中的黄色字节,然后dst_pixel+w就是指向的dst中的黄色字节,h为0,所以src_pixel+h还是src中的黄色字节,然后交换数据。等到执行完成之后src中的图像数据就变为了dst中的图像数据的方向了,然后传入内核显示,这样图像就顺时针旋转了90度。  这样旋转recovery界面的修改就完成了,但是创建的旋转后的GRSurface在退出recover的时候是需要清理的,为了方便,就新建了一个文件来存储旋转的代码,然后在minui\graphics_fbdev.cpp中来调用会方便一点,修改如下:  新建graphic_rotate.cpp和graphic_rotate.h文件到minui目录下: graphic_rotate.cpp的内容如下:

#include #include #include #include #include #include #include #include #include #include #include #include #include "graphics.h" #include GRSurface __gr_canvas; GRSurface* gr_canvas = NULL; int rotate_index=-1; static void print_surface_info(GRSurface *s, const char *name) { printf("[graphics] %s > Height:%d, Width:%d, PixelBytes:%d, RowBytes:%d, Size:%d, Data: 0x%08" PRIxPTR "\n", name, s->height, s->width, s->pixel_bytes, s->row_bytes, s->height* s->row_bytes, (uintptr_t) s->data); } static int rotate_config(GRSurface *gr_draw) { if (rotate_index gr_canvas = &__gr_canvas; memcpy(gr_canvas, gr_draw, sizeof(GRSurface)); if (rotate_config(gr_draw)%2) { swap(gr_canvas->width, gr_canvas->height, int); gr_canvas->row_bytes = gr_canvas->width * gr_canvas->pixel_bytes; } gr_canvas->data = (unsigned char*) malloc(gr_canvas->height * gr_canvas->row_bytes); if (gr_canvas->data == NULL) { printf("[graphics] rotate_canvas_init() malloc gr_canvas->data failed\n"); gr_canvas = NULL; return; } memset(gr_canvas->data, 0, gr_canvas->height * gr_canvas->row_bytes); print_surface_info(gr_draw, "gr_draw"); print_surface_info(gr_canvas, "gr_canvas"); } // Cleanup the canvas void rotate_canvas_exit(void) { if (gr_canvas) { if (gr_canvas->data) free(gr_canvas->data); free(gr_canvas); } gr_canvas=NULL; } // Return the canvas object GRSurface *rotate_canvas_get(GRSurface *gr_draw) { // Initialize the canvas, if it was not exist. if (gr_canvas==NULL) rotate_canvas_init(gr_draw); return gr_canvas; //return gr_draw; } // Surface Rotate Routines static void rotate_surface_0(GRSurface *dst, GRSurface *src) { memcpy(dst->data, src->data, src->height*src->row_bytes); } static void rotate_surface_270(GRSurface *dst, GRSurface *src) { int v, w, h; unsigned int *src_pixel; unsigned int *dst_pixel; for (h=0, v=src->width-1; hheight; h++, v--) { for (w=0; wwidth; w++) { dst_pixel = (unsigned int *)(dst->data + dst->row_bytes*h); src_pixel = (unsigned int *)(src->data + src->row_bytes*w); *(dst_pixel+w)=*(src_pixel+v); } } } static void rotate_surface_180(GRSurface *dst, GRSurface *src) { int v, w, k, h; unsigned int *src_pixel; unsigned int *dst_pixel; for (h=0, k=src->height-1; hheight && k>=0 ; h++, k--) { dst_pixel = (unsigned int *)(dst->data + dst->row_bytes*h); src_pixel = (unsigned int *)(src->data + src->row_bytes*k); for (w=0, v=src->width-1; wwidth && v>=0; w++, v--) { *(dst_pixel+w)=*(src_pixel+v); } } } static void rotate_surface_90(GRSurface *dst, GRSurface *src) { int w, k, h; unsigned int *src_pixel; unsigned int *dst_pixel; for (h=0; hheight; h++) { for (w=0, k=src->height-1; wwidth; w++, k--) { dst_pixel = (unsigned int *)(dst->data + dst->row_bytes*h); src_pixel = (unsigned int *)(src->data + src->row_bytes*k); *(dst_pixel+w)=*(src_pixel+h); } } } typedef void (*rotate_surface_t) (GRSurface *, GRSurface *); rotate_surface_t rotate_func[4]= { rotate_surface_0, rotate_surface_90, rotate_surface_180, rotate_surface_270 }; // rotate and copy src* surface to dst surface void rotate_surface(GRSurface *dst, GRSurface *src) { rotate_surface_t rotate; rotate=rotate_func[rotate_config(dst)]; rotate(dst, src); }

graphic_rotate.h文件内容如下:

#ifndef GRAPHICS_ROTATE_H_ #define GRAPHICS_ROTATE_H_ void rotate_canvas_exit(void); void rotate_canvas_init(GRSurface *gr_draw); void rotate_surface(GRSurface *dst, GRSurface *src); GRSurface *rotate_canvas_get(GRSurface *gr_draw); #endif

在minui\graphics_fbdev.cpp中的修改如下:

diff --git a/bootable/recovery/minui/graphics_fbdev.cpp b/bootable/recovery/minui/graphics_fbdev.cpp old mode 100644 new mode 100755 index 746f42a..ae7a7f0 --- a/bootable/recovery/minui/graphics_fbdev.cpp +++ b/bootable/recovery/minui/graphics_fbdev.cpp @@ -27,7 +27,9 @@ #include #include "minui/minui.h" - +//hekh add for rotateCanvas +#include "graphic_rotate.h" + MinuiBackendFbdev::MinuiBackendFbdev() : gr_draw(nullptr), fb_fd(-1) {} void MinuiBackendFbdev::Blank(bool blank) { @@ -134,14 +136,19 @@ GRSurface* MinuiBackendFbdev::Init() { SetDisplayedFramebuffer(0); printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height); - Blank(true); Blank(false); - - return gr_draw; + + //return gr_draw; + //hekh add for rotateCanvas + return rotate_canvas_get(gr_draw); } GRSurface* MinuiBackendFbdev::Flip() { + //hekh add for rotateCanvas + rotate_surface(gr_draw, rotate_canvas_get(gr_draw)); if (double_buffered) { // Change gr_draw to point to the buffer currently displayed, // then flip the driver so we're displaying the other buffer @@ -152,13 +159,16 @@ GRSurface* MinuiBackendFbdev::Flip() { // Copy from the in-memory surface to the framebuffer. memcpy(gr_framebuffer[0].data, gr_draw->data, gr_draw->height * gr_draw->row_bytes); } - return gr_draw; + //return gr_draw; + //hekh add for rotateCanvas + return rotate_canvas_get(gr_draw); } MinuiBackendFbdev::~MinuiBackendFbdev() { close(fb_fd); fb_fd = -1; - + //hekh add for rotateCanvas + rotate_canvas_exit(); if (!double_buffered && gr_draw) { free(gr_draw->data); free(gr_draw);

然后在minui目录下的Android.mk文件中添加

diff --git a/bootable/recovery/minui/Android.mk b/bootable/recovery/minui/Android.mk old mode 100644 new mode 100755 index 4dfc65f..43444f1 --- a/bootable/recovery/minui/Android.mk +++ b/bootable/recovery/minui/Android.mk @@ -23,10 +23,14 @@ LOCAL_SRC_FILES := \ graphics_fbdev.cpp \ resources.cpp \ +#hekh add +LOCAL_SRC_FILES += graphic_rotate.cpp + LOCAL_WHOLE_STATIC_LIBRARIES := \ libadf \ libdrm \ - libsync_recovery + libsync_recovery \ + libbase LOCAL_STATIC_LIBRARIES := \ libpng \ 6.2 Recovery升级界面的logo更改及字符修改和添加 6.2.1 升级界面的logo替换

 recovery的logo是指升级过程中的动画界面,原生的系统是一个安卓小机器人的动图,848上就是那个在动的圆,其实升级过程中的动图logo就是一组图片,然后循环播放这一组图片,848上加载升级logo的图片代码在4.6.2章节中的LoadAnimation方法加载的,具体代码如下:

void ScreenRecoveryUI::LoadAnimation() { std::unique_ptr dir(opendir("/res/images"), closedir); dirent* de; std::vector intro_frame_names; std::vector loop_frame_names; while ((de = readdir(dir.get())) != nullptr) { int value, num_chars; if (sscanf(de->d_name, "intro%d%n.png", &value, &num_chars) == 1) { intro_frame_names.emplace_back(de->d_name, num_chars); } else if (sscanf(de->d_name, "loop%d%n.png", &value, &num_chars) == 1) { loop_frame_names.emplace_back(de->d_name, num_chars); } } ..... }

 这里可以看到加载的是loop%d%n.png的图片,所以要换logo的话直接制作一组logo的图片替换调loop%d%n.png图片就可以了,loop图片在res-hdpi\images\目录下,具体名字就是loop00000.png、loop00001.png这样的,logo动画是多张8位深度png的图片,在linux下用imagemaic工具convert转换生成,具体命令如下:

convert src.png -depth 8 -colorspace gray dst.png 6.2.2 升级界面字符修改及添加

同理字符的修改和添加也是差不多的,加载字符资源的方法如下:

void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface); if (result if (!visible) { return kNoAction; } + printf("key=%d",key); + const int KEYCODE_1 = 2; switch (key) { case KEY_DOWN: case KEY_VOLUMEDOWN: return kHighlightDown; case KEY_UP: case KEY_VOLUMEUP: return kHighlightUp; + case KEYCODE_1: case KEY_ENTER: case KEY_POWER: return kInvokeItem; default: // If you have all of the above buttons, any other buttons // are ignored. Otherwise, any button cycles the highlight. return ui_->HasThreeButtons() ? kNoAction : kHighlightDown; } }

 在这里就可以更改按键策略了,比如添加数字键1为选择键的话则按上面的代码更改就可以了,这里需要注意的是,recovery模式下的键值和android的键值不同,每个按键的键值具体可以查看2000_6a848_dtmb_Oreo_Smart\bionic\libc\kernel\uapi\linux\input-event-codes.h文件里面的定义。

6.4 Recovery的菜单选项添加

Recovery显示的菜单选项都是在device.cpp中添加的,添加一个旋转选项对应的代码如下:

static const char* MENU_ITEMS[] = { "Reboot system now", "Reboot to bootloader", "Apply update from ADB", "Apply update from SD card", //MStar patch begin "Apply update from CACHE", //MStar patch end "Wipe data/factory reset", #ifndef AB_OTA_UPDATER "Wipe cache partition", #endif // !AB_OTA_UPDATER "Mount /system", "View recovery logs", "Run graphics test", "Power off", + //Hekh patch begin + "Rotate", + //Hekh patch end NULL, };

然后添加选择它后的action:

static const Device::BuiltinAction MENU_ACTIONS[] = { Device::REBOOT, Device::REBOOT_BOOTLOADER, Device::APPLY_ADB_SIDELOAD, Device::APPLY_SDCARD, //MStar patch begin Device::APPLY_CACHE, //MStar patch end Device::WIPE_DATA, #ifndef AB_OTA_UPDATER Device::WIPE_CACHE, #endif // !AB_OTA_UPDATER Device::MOUNT_SYSTEM, Device::VIEW_RECOVERY_LOGS, Device::RUN_GRAPHICS_TEST, Device::SHUTDOWN, + //Hekh patch begin + Device::ROTATE, + //Hekh patch end };

 然后在会在prompt_and_wait方法中通过GetMenuItems获取到ACTION,prompt_and_wait方法在4.7章节有介绍,然后在prompt_and_wait方法中添加获取到的Action为ROTATE时的实现,这里就是设置persist.sys.rotation属性后重新调用minui的gr_init方法就可以了。

七、小知识 如果recovery有问题启动不了,那么会卡在开机log的第二帧上;recovery就是recovery.cpp编译生成的可执行文件(具体可以看recovery下的Android.mk);平时使用的adb就是PC端adb通过socket连接sbin/adbd,执行的命令都是这个adbd执行的;


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3